import type { Feature, Point, LineString, GeoJsonProperties } from 'geojson'
import RBush from 'rbush'
import { BaseLineLayer, BaseSymbolLayer, type LineStyle, type SymbolStyle } from '@/map'
import { lngLatToSegDist } from '@/geom/euclidean'
import type { LngLat } from '@/geom/great-circle'

export interface Segment {
  start: LngLat
  end: LngLat
}

export function segment2Feature<G extends GeoJsonProperties>(
  seg: Segment,
  properties: G
): Feature<LineString, G> {
  return {
    type: 'Feature',
    geometry: {
      type: 'LineString',
      coordinates: [
        [seg.start.lng, seg.start.lat],
        [seg.end.lng, seg.end.lat]
      ]
    },
    properties
  }
}

export interface SegmentLayerOptions {
  id: string
  name: string
  style: LineStyle
  segment: Segment[]
  visible?: boolean
  minZoom?: number
}

export class SegmentLayer extends BaseLineLayer<Segment> {
  constructor(opts: SegmentLayerOptions) {
    super(
      {
        id: opts.id,
        name: opts.name,
        style: opts.style,
        visible: opts.visible,
        minZoom: opts.minZoom,
        getFeature: (t) => segment2Feature(t, null)
      },
      opts.segment
    )
  }
}

export function segmentMidPoint2Feature<G extends GeoJsonProperties>(
  s: Segment,
  properties: G
): Feature<Point, G> {
  return {
    type: 'Feature',
    geometry: {
      type: 'Point',
      coordinates: [(s.start.lng + s.end.lng) / 2, (s.start.lat + s.end.lat) / 2]
    },
    properties
  }
}

export interface SegmentMidPointLayerOptions<S extends Segment> {
  id: string
  name: string
  style: SymbolStyle
  iconRotate?: number | mapboxgl.Expression
  minZoom?: number
  visible?: boolean
  getText: (s: S) => string
  data?: S[]
}

export class SegmentMidPointLayer<S extends Segment> extends BaseSymbolLayer<S> {
  constructor(opts: SegmentMidPointLayerOptions<S>) {
    super(
      {
        id: opts.id,
        name: opts.name,
        style: opts.style,
        iconRotate: opts.iconRotate,
        minZoom: opts.minZoom,
        visible: opts.visible,
        getFeature: (s) => {
          return segmentMidPoint2Feature(s, { text: opts.getText(s) })
        },
        textField: ['get', 'text']
      },
      opts.data
    )
  }
}

export interface SegmentItem<T> extends Segment {
  data: T[]
  minX: number
  minY: number
  maxX: number
  maxY: number
}

export function segmentItemOf<T>(
  start: LngLat,
  end: LngLat,
  data: T[] | undefined = undefined
): SegmentItem<T> {
  return {
    start,
    end,
    data: data || [],
    minX: Math.min(start.lng, end.lng),
    maxX: Math.max(start.lng, end.lng),
    minY: Math.min(start.lat, end.lat),
    maxY: Math.max(start.lat, end.lat)
  }
}

export class SegmentItemIndex<T> {
  private idx: RBush<SegmentItem<T>>
  constructor(segments: SegmentItem<T>[]) {
    this.idx = new RBush(16)
    this.idx.load(segments)
  }
  public search(lng: number, lat: number, radius: number): Set<T> | null {
    radius = radius / 2
    const r = this.idx.search({
      minX: lng - radius,
      minY: lat - radius,
      maxX: lng + radius,
      maxY: lng + radius
    })
    if (!r) {
      return null
    }
    const pt = { lng, lat }
    const ret = new Set<T>()
    for (const t of r) {
      const d1 = lngLatToSegDist(pt, t.start, t.end)
      if (d1 <= radius) {
        for (const tt of t.data) {
          ret.add(tt)
        }
      }
    }
    return ret
  }
}
